<?php
    if (!defined('__IS_LEAF__')) exit('Access Denied!');
    
    /**
     * Dao层基类
     * 可用操作如：
     * 插入数据：$this->data()->insert()、$this->data=array(...)，$this->insert()
     * 更新数据：$this->username='xxx',$this->id=1,$this->update()或$this->where()->data()->update()
     * 删除数据：$this->delete(1,2,3)或$this->id=1;$this->delete()或$this->where('id in (1,2,3)')->delete()
     * 查询数据：$this->select(1,2,3)或$this->id=1;$this->select()或$this->where()->select();或$this->...find()等
     * 数据库查询统计，借助的是每次做CURD，则将最近一次的信息放入到$_dbStats中
     */
    class Dao extends Base{

        protected $_db;                     //数据库连接对象
        protected $_tableName;              //表名
        protected $_isNewRecord;            //此次操作是否要新增记录
        protected $_tableValid = true;      //表的存在性是否已被验证
        protected $_primaryKey;             //主键字段
        public    $pk = '';                 //主键
        public    $fields = '';             //字段列表，数组形式
        public    $where = '';              //where条件
        public    $limit = '';              //limit条件
        public    $order = '';              //order条件
        public    $group = '';              //group条件
        public    $data;                    //数据集
        protected $_values = '';            //curd数据时的隐式的值，如查询时，从where数组中择取的值
        protected $_executeData=false;       //
        protected $_fieldsData = array();   //外部对属性赋值的数据数组，用于更新、插入、删除或查询
        private   $_dbStats='';              //数据库操作统计

        //实例化Dao模型
        public function __construct($tableName){
            if(!defined('__DB__STATS__')) define('__DB_STATS__', C('db_stats'));
            //获取表名
            $this->_tableName = $tableName;
            //初始化数据库操作统计
            if(__DB_STATS__) $this->_setDbStats();
            //执行Db数据库驱动类初始化
            $this->_initDb($this->_tableName);
            //设置通过查询数据库，dao的必须属性，如主键、字段等
            if($this->_primaryKey===null || $this->fields===null){
                //获取病设置对象的字段属性结构
                $this->_setTableStructure();
                //设置CURD之前的基本属性，如$this->order为主键递减
                $this->_setAttributes();
            }
        }

        /**
         * 初始化数据库操作统计
         * @param string $type 统计类型，curd的一种类型
         */
        final private function _setDbStats($type='', $time=''){
            if(empty($type)){
                $this->_dbStats=array(
                        'total' => array(
                                'run_time'  => 0,
                                'mem_usage' => 0,
                            ),
                        'select'=> array(),
                        'insert'=> array(),
                        'update' => array(),
                        'delete' => array(),
                    );
            }
        }

        /**
         * 返回MysqlPDO对象
         */
        private function _initDb($tableName){
            $db = DbFactory::getDbInstance($tableName);
            $this->_db = $this->_tableValid ? $db : false;
            if($this->_db === false) $this->error($tableName.L('dao_error'));
        }

        /**
         * 获取表结构构成
         */
        private function _setTableStructure(){
            //获取表结构
            $tableStrucSql = "SHOW FULL FIELDS FROM {$this->_tableName}";
            try{
                $tableStructure = $this->_db->query($tableStrucSql);
            }catch(PDOException $e){
                $this->error($e->getMessage());
            }
            //获取字段集合和主键
            $fields = array();
            $primaryKey = '';
            foreach ($tableStructure as $key => $value) {
                //如果字段集合为null，则获取字段集合
                if($value['Key']==='PRI'){
                    $primaryKey = $value['Field'];
                }
                $fields[] = '`'.$value['Field'].'`';
            }
            $this->_primaryKey = $primaryKey;
            $this->fields = $fields;
        }

        /**
         * 此方法为空方法，可拓展
         */
        private function _setAttributes(){
        }

        /**
         * field字段选取
         */
        public function fields($inputField=false){
            $fields = '';
            //如果以数组的形式设置$this->fields
            if (is_array($inputField) && count($inputField)>0) {
                //提取字段，判断字段是否在表的字段列表，过滤非法字段
                foreach ($inputField as $key => $value) {
                    if( is_string($key) && in_array( '`'.$key.'`', $this->fields) ){
                        $fields .= '`'.$key.'` AS `'.$value.'`,';
                    //fields( array('id'=>'bid') )别名形式
                    }elseif( is_int($key) && in_array($value, $this->fields) ){
                        $fields .= '`'.$value.'`,';
                    }
                }
                $fields = rtrim($fields, ',');
            //如果以字符串的形式设置$this->fields
            }elseif (is_string($inputField) && !empty($inputField)) {
                $fields = &$inputField;
            }
            !empty($fields) && $this->fields = $fields;
            return $this;
        }

        /**
         * where条件
         * @param mixed $where 字符串或数组类型
         */
        final public function where($where=null){
            $whereArr = func_get_args();
            $res  =$this->parseWhere($whereArr);
            return $res;
        }

        /**
         * 解析where函数的条件
         */
        final private function parseWhere($whereArr){
            $count = count($whereArr);
            //如果$where条件为数组类型，则为where( array('id','>',1), array('address','=','朝阳') )等类型格式
            if( $count>0 && !is_string($whereArr[0]) ){
                $whereStr = ' WHERE ';
                $executeData = array();
                foreach ($whereArr as $key => $value) {
                    //如果where条件循环value为“AND”或“OR”，则跳出循环，默认使用AND连接
                    if(is_string($value)) continue;
                    //取当前单元的下一个单元，$type变量，用于判断是AND连接还是OR连接
                    //比如当前为 array('id','>',1)，其下一个是字符串，则认为是
                    $type = false;
                    if(isset($whereArr[$key+1]) && !empty($whereArr[$key+1]) && is_string($whereArr[$key+1])){
                        $type = 'OR';
                    }else{
                        $type = 'AND';
                    }
                    //对between、in等做不同的处理
                    switch ($value[1]) {
                        //如果为between
                        case 'BETWEEN':
                        case 'between':
                            $betweenValues  = explode(',', $value[2]);
                            $whereStr .= '`'.$value[0].'` '.BETWEEN.' :'.$value[0].$key.$betweenValues[0].' AND :'.$value[0].$key.$betweenValues[1].' '.$type.' ';
                            $betweenValues[0] = ($betweenValues[0]==intval($betweenValues[0])) ? intval($betweenValues[0]) : intval($betweenValues[0]);
                            $betweenValues[1] = ($betweenValues[1]==intval($betweenValues[1])) ? intval($betweenValues[1]) : intval($betweenValues[1]);
                            $executeData[':'.$value[0].$key.$betweenValues[0]] = $betweenValues[0];
                            $executeData[':'.$value[0].$key.$betweenValues[1]] = $betweenValues[1];
                            break;
                        //如果为in，in查询特殊，无法直接将 in (1,2,3)中“(1,2,3)”整体做绑定参数
                        case 'IN':
                        case 'in':
                            $inValues = explode(',', $value[2]);
                            foreach ($inValues as $inKey => $inValue) {
                                $inParam .= ':in'.$inKey.',';
                                $inValue = ($inValue==intval($inValue)) ? intval($inValue) : intval($inValue);
                                $executeData[':in'.$inKey] = $inValue;
                            }
                            $inParam = trim($inParam, ',');
                            $whereStr .= '`'.$value[0].'` IN ('.$inParam.') '.$type.' ';
                            break;
                        //
                        default:
                            $whereStr .= '`'.$value[0].'` '.$value[1].' :'.$value[0].$key.' '.$type.' ';
                            $executeData[':'.$value[0].$key] = $value[2];
                            break;
                    }
                }
                //因为$whereStr是foreach之后拼接的，所以最后会多一个AND或OR，需要去除
                $this->where = trim($whereStr, $type.' ');
                $this->_executeData = $executeData;
            }elseif( $count>0 && !empty($whereArr[0]) ){
                $this->where = 'WHERE '.$whereArr[0];
            }else{
                //如果没有调用where()函数，或调用where函数的时候传递了空值，则$whereStr为false，只要为假，则报错！
                $this->where = false;
            }
            return $this;
        }

        /**
         * 待处理的数据集，适用于insert、update
         * @param array $data数据集合
         */
        public function data(array $data){
            //对数据集进行合法性过滤

        }

        public function limit($limit=false){
            $limitStr = 'limit ';
            //如果没有传递参数，默认limit 1
            if($limit===false){
                $limitStr .= '1';
            //如果为数字的形式
            }elseif( is_numeric($limit) && $limit>0 ){
                $limitStr .= $limit;
            //如果参数为数组
            }elseif( is_array($limit) && count($limit)==2 ){
                $limitStr .= $limit[0].','.$limit[1];
            //如果参数为字符串
            }elseif ( is_string($limit) ) {
                $limitStr .= $limit;
            }
            $this->limit = $limitStr;
            return $this;
        }

        /**
         * 排序方式，默认使用主键的递减排序
         */
        public function order($order=''){
            //如果$order有值
            if(is_string($order) && !empty($order)){
                $order = 'ORDER BY '.$order;
            }else{
                $order = 'ORDER BY `'.$this->_primaryKey.'` desc';
            }
            $this->order = $order;
            return $this;
        }

        public function group($group=''){
            if(!empty($group)){
                //如果是非空字符串
                if( is_string($group) ){
                    $group = 'GROUP BY '.$group;
                }elseif( is_array($group) ){
                    $group = 'GROUP BY'.implode(',', $group);
                }
            }
            $this->group = $group;
            return $this;
        }

        /**
         * 更新数据
         */
        public function update($update=null){
            if(is_null($update)){
                $res = $this->_db->update($this->_tableName, $this->_values);
            }
        }

        /**
         * 插入数据
         */
        public function insert(){
            $res = $this->_db->insert($this->_tableName, $this->fields, $this->_values);
        }

        public function delete($delte=null){
            $res = $this->_db->delete($thi->_tableName, $this->where);
        }

        /**
         * select查询数据
         * @param string $pks 想要获取的主键列表
         */
        public function select(){
            //如果select有正确的参数值，可实现$this->select(1,2,3)的形式直接获取主键在1,2,3中的记录集
            //或$this->pk='1,2';$this->select()的调用形式
            if( count( $args = func_get_args() )>0 || !empty($this->pk) ){
                //如果同时设置了$this->pk和select($args)的$args参数，则查询以$this->pk为先
                $args = !empty($this->pk) ? array($this->pk) : $args;
                $this->setPksOfWhere($args);
            }
            //获取字段集合
            $fields = $this->_getDaoFields();
            //获取操作的表名
            $table = $this->_tableName;
            //获取解析后的where条件，需要调用函数$this->getDaoWhere，防止用户没有调用where()函数
            $where = $this->_getDaoWhere();
            //获取limiit数值
            $limit = $this->_getDaoLimit();
            //获取解析后的order
            $order = $this->_getDaoOrder();
            //获取解析后的group
            $group = $this->_getDaoGroup();
            $values = $this->_executeData;
            $res  = $this->_db->select($table, $fields, $where, $group, $order, $limit, $values);
            //如果开启了数据库信息统计，则执行统计信息添加操作
            if(__DB_STATS__){
                $sqlStats = $this->getLastQueryStat();
                $this->_dbStatsAdd('select', $sqlStats);
            }
            return count($res)>0 ? $res : false;
        }

        /**
         * 当为select()赋值的时候，调用此函数，可实现$dao->select(1,2,4)则取出主键为1,2,4的记录集这种简便用法
         */
        final private function setPksOfWhere($args){
            if( ($count = count($args) )>0 ){
                $pks = implode(',', $args);
                $where = $this->where;
                /*为了防止出现多个where,如：where address='北京'where `id` IN ('1')的情况，先判断之前是否调用过where
                如果调用过，则为$this->where的值不为空*/
                $this->where( array($this->_primaryKey, 'IN', $pks) );
                if( empty($where) ){
                }else{
                    $newWhere = $this->where;
                    //如果之前调用过$this->where，本次的新$this->where会又有一个where字符串，需要去掉
                    $newWhere = str_replace('WHERE', ' AND', $newWhere);
                    $this->where = $where.$newWhere;
                }
                //调用此方法之后，limit就不能取默认值了
                ($count==1) ? $this->limit(count(explode(',', $args[0]))) : $this->limit(count($args));
            }
        }

        /**
         * 获取单条结果集，结果集记录是一维关联数组
         * @return array $result[0] 一位数组结果集
         */
        public function find($pkValue=false){
            //设置智能获取一条结果集
            $this->limit = 'limit 1';
            //如果设置了$this->pk，则以$this->pk为主
            if( !empty($this->pk) ){
                $pkValue = intval($this->pk);
                //此处需要重置$this->pk=''，否则下面select函数被调用，又会重新调用$this->pk
                $this->pk = '';
            }
            //当find函数有参数的时候，实际上是获取主键的结果，如find(1)则查询主键为1的结果集
            if( (is_int($pkValue) && $pkValue>0) ){
                if( empty($this->where) ){
                    $this->where( array($this->_primaryKey, '=', $pkValue) );
                }else{
                    $prepareValue = ':'.$this->_primaryKey.time().rand(1,999);
                    $this->where .= ' AND `'.$this->_primaryKey.'`='.$prepareValue;
                    $this->_executeData[$prepareValue] = $pkValue;
                }
            }
            //调用select查询结果集
            $result = $this->select();
            return count($result[0])>0 ? $result[0] : false;
        }

        /**
         * 获取单条记录中某一个字段的值
         */
        public function getField($field){
            if( in_array('`'.$field.'`', $this->fields) ){
                //设置$this->fields为单一字段，节省资源
                $this->fields = array( '`'.$field.'`' );
                $result = $this->find();
                return $result[$field];
            }else{
                return false;
            }
        }

        /**
         * 统计结果集的数量
         */
        public function count($field=false){
            //将limit和order设置为空
            $this->limit = '';
            $this->order = '';
            $prepareFields = $this->fields;
            //设置统计字段，默认为所有
            if($field===false){
                $this->fields = 'COUNT(*) as leaf_count';
                $res = $this->find();
                $res = $res['leaf_count'];

            //统计指定字段的结果
            //判断字段的有效性
            }elseif(is_string($field) && in_array('`'.$field.'`', $this->fields)){
                $this->fields = 'COUNT(`'.$field.'`) AS `'.$field.'`';
                P($this->fields);
                $res = $this->find();
                $res = $res[$field];
            }
            return $res;
        }

        /**
         * 如果开启了db_stats，此函数可被调用，用于添加统计信息
         * @access private
         * @param string $type 添加类型，值为select、insert、update或delete
         * @param array $sqlStats 单条sql的统计信息
         */
        final private function _dbStatsAdd($type, $sqlStats){
            //如果开启了数据库信息统计，则进行统计操作
            if(__DB_STATS__){
                $this->_dbStats['total']['run_time']   += $sqlStats['run_time'];
                $this->_dbStats['total']['mem_usage']  += $sqlStats['mem_usage'];
                $this->_dbStats[$type]['run_time']  += $sqlStats['run_time'];
                $this->_dbStats[$type]['mem_usage'] += $sqlStats['mem_usage'];
                $this->_dbStats[$type]['detail'][] = $sqlStats;
            }
        }

        /**
         * 执行有结果集的查询
         */
        public function query($sql){
            return $this->_db->query($sql);
        }

        /**
         * 
         */
        private function _executeData(){

        }

        /**
         * 执行删除、增加或修改的查询
         */
        public function exec($sql){
            return $this->_db->exec($sql);
        }

        final public function getLastSql(){
            return $this->_db->getLastSql();
        }

        final public function getLastQueryStat(){
            return $this->_db->getLastQueryStat();
        }

        public function lastInsertId(){
            
        }


        /**
         * 获取当前Dao或Service的表名
         */
        public function getThisTable(){
            return $this->_tableName;
        }

        /**
         * 获取当前Dao或service的字段集合
         * 主要是判断用户是否调用过fields函数，如果没有，则系统进行设置
         * 如果发现$this->fields为数组，则说明从Dao初始化开始就没被调用过（调用$this->fields()函数后一定是字符串）
         * 那么就自动转换为字符串的字段集合
         */
        final private function _getDaoFields(){
            $fields = $this->fields;
            if(is_array($fields)){
                $fields = implode(',', $fields);
                $this->fields = $fields;
            }
            return $this->fields;
        }

        /**
         * 如果$this->where为false，则说明用户使用了$this->where()且没有传值这种不合法的做法。
         * 如果$this->where为空字符串，则表示没有调用过$this->where()
         */
        final private function _getDaoWhere(){
            //没有设置where的时候，判断是否为AR方式
            if( empty($this->where) ){
                /*//如果是AR方式的查询
                if( count($this->_fieldsData)>0 ){
                    if( empty($this->where) ) $this->where = 'WHERE ';
                    foreach ($this->_fieldsData as $key => $value) {
                        //判断AR方式的参数，是否为合法的字段
                        if( stripos($this->fields, $key)!==false ){
                            //$this->id=1和$this->id='>1'的情况
                            $arValue = ($value==intval($value)) ? '='.$value : $value;
                            $this->where .= $key.$arValue.' AND ';
                        }
                    }
                    $this->where = rtrim($this->where,'AND ');
                }else{
                    $this->error( L('lose_of_where') );
                }*/
            //以ORM方式设置的where属性，值为数组
            }elseif( is_array($this->where) && count($this->where)>0 ){
                $this->where($this->where);
            //以ORM方式设置的where属性，其值为字符串
            }elseif( is_string($this->where) && stripos($this->where, 'where')===false ){
                $this->where = 'WHERE '.$this->where;
            }
            return $this->where;
        }

        /**
         * 获取limit数值
         * 主要是判断用户是否调用过limit函数，此函数意义不大，为方便拓展
         */
        final private function _getDaoLimit(){
            //如果没有设置，调取默认的空
            if( $this->limit=='' ){
                $this->limit = '';
            //如果是ORM以属性方式外部调用，值为数组
            }elseif( is_array($this->limit) && count($this->limit)>0 ){
                $this->limit($this->limit);
            //如果是ORM以属性方式外部调用，值为字符串（检测字串有没有limit）
            }elseif( is_string($this->limit) && stripos($this->limit, 'limit')!==false ){
                //#code：这个else从句其实可去掉
            //如果是ORM以属性方式外部调用，值为整型数字或等同于整型数字的字符串，如1和'1'
            }elseif( intval($this->limit) == $this->limit ){
                $this->limit = 'LIMIT '.$this->limit;
            }
            return $this->limit;
        }

        /**
         * ORDER BY的值
         * 主要是判断用户是否调用过limit函数，如果没有（字符串值为空字串），则设置默认为1
         */
        final private function _getDaoOrder(){
            //如果没有设置，以默认的主键递减排序
            if( $this->order=='' ){
                return $this->order = 'ORDER BY `'.$this->_primaryKey.'` desc';
            //如果是ORM以属性方式外部调用，值为字符串（检测其中是否有order）
            }elseif( is_string($this->order) && stripos($this->order, 'order' )===false ){
                $this->order = 'ORDER BY '.$this->order;
            //同上，stripos结果不是false
            }elseif(is_string($this->order) && stripos($this->order, 'order' )!==false){
            
            //ORM属性调用方式，参数值为数组
            }elseif( is_array($this->order) && count($this->order)>1 ){
                $this->order($this->order);
            }
            return $this->order;
        }

        final private function _getDaoGroup(){
            if(!empty($this->group)){
                //ORM方式调用$this->group，值为字符串
                if( is_string($this->group) && stripos($this->group, 'group')===false ){
                    $this->group = 'GROUP BY '.$this->group;
                }elseif( is_array($this->group) && count($this->group)>0 ){
                    $this->group = 'GROUP BY '.implode(',', $this->group);
                }
            }
            return $this->group;
        }

        /**
         * 获取最近一次对数据库进行操作的信息，用于调试或统计及日志记录等
         */
        public function getLastQueryInfo(){

        }


        public function __set($call, $value){
            //转化值，将name转化为`name`类型
            $field = '`'.$call.'`';
            //如果外部设置的属性在字段列表，则进行属性赋值
            if(in_array($field, $this->fields)){
                $this->_fieldsData[$field] = $value;
            }
        }

        public function __get($call)
        {
            parent::__get($call);
        }

        public function __destruct(){
            //如果开启了sql记录，则将数据库统计信息写入文件
            if(__DB_STATS__){
                Log::sqlLogRecord($this->_dbStats);
            }
            P($this->_dbStats);
        }

    }
